/****************************************************************************** * Copyright (C) 2015 Yevgeny Krasik * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * ******************************************************************************/ package com.github.ykrasik.jaci.cli.javafx; import com.github.ykrasik.jaci.cli.Cli; import com.github.ykrasik.jaci.cli.CliShell; import com.github.ykrasik.jaci.cli.commandline.CommandLineManager; import com.github.ykrasik.jaci.cli.gui.CliGui; import com.github.ykrasik.jaci.cli.hierarchy.CliCommandHierarchy; import com.github.ykrasik.jaci.cli.hierarchy.CliCommandHierarchyImpl; import com.github.ykrasik.jaci.cli.javafx.commandline.JavaFxCommandLineManager; import com.github.ykrasik.jaci.cli.javafx.gui.JavaFxCliGui; import com.github.ykrasik.jaci.cli.javafx.output.JavaFxCliOutput; import com.github.ykrasik.jaci.cli.output.CliPrinter; import com.github.ykrasik.jaci.hierarchy.CommandHierarchyDef; import com.github.ykrasik.jaci.reflection.JavaReflectionAccessor; import com.github.ykrasik.jaci.util.exception.SneakyException; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.control.Label; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.input.KeyEvent; import java.net.URL; import java.util.Objects; /** * A CLI builder for JavaFx.<br> * <br> * Uses a default layout, unless a custom layout is provided. * A custom layout must have the following: * <ul> * <li>A {@link TextArea} with a fxml id 'cliOutput' that will be used as the terminal output.</li> * <li>A {@link Label} with a fxml id 'workingDirectory' that will be used to display the 'workingDirectory' label.</li> * <li>A {@link TextField} with a fxml id 'commandLine' that will be used to read and display the command line.</li> * </ul> * * The default layout may also be customized with css: * <ul> * <li>The output is a {@link TextArea} with a css id '#cliOutput'</li> * <li>The working directory is a {@link Label} with a css id '#workingDirectory'</li> * <li>The command line is a {@link TextField} with a css id '#commandLine'</li> * </ul> * * @author Yevgeny Krasik */ public class JavaFxCliBuilder { static { // Set reflection to the Java API. JavaReflectionAccessor.install(); } private final CommandHierarchyDef.Builder hierarchyBuilder = new CommandHierarchyDef.Builder(); // TODO: Add Max textArea size. private URL fxmlUrl; private int maxCommandHistory = 30; /** * Process the classes and add any commands defined through annotations to this builder. * Each class must have a no-args constructor. * * @param classes Classes to process. * @return {@code this}, for chaining. */ public JavaFxCliBuilder processClasses(Class<?>... classes) { hierarchyBuilder.processClasses(classes); return this; } /** * Process the objects' classes and add any commands defined through annotations to this builder. * * @param instances Objects whose classes to process. * @return {@code this}, for chaining. */ public JavaFxCliBuilder process(Object... instances) { hierarchyBuilder.process(instances); return this; } /** * Set the maximum amount of command history entries to keep. * * @param maxCommandHistory Max command history entries to keep. * @return {@code this}, for chaining. */ public JavaFxCliBuilder setMaxCommandHistory(int maxCommandHistory) { this.maxCommandHistory = maxCommandHistory; return this; } /** * Set a path to a .fxml file containing a custom layout. * A custom layout must have the following: * <ul> * <li>A {@link TextArea} with a fxml id 'cliOutput' that will be used as the terminal output.</li> * <li>A {@link Label} with a fxml id 'workingDirectory' that will be used to display the 'workingDirectory' label.</li> * <li>A {@link TextField} with a fxml id 'commandLine' that will be used to read and display the command line.</li> * </ul> * * @param fxmlPath Path to .fxml file containing the custom layout. * @return {@code this}, for chaining. */ public JavaFxCliBuilder setFxmlPath(String fxmlPath) { return setFxmlUrl(Thread.currentThread().getContextClassLoader().getResource(fxmlPath)); } /** * Set the url to a .fxml file containing a custom layout. * A custom layout must have the following: * <ul> * <li>A {@link TextArea} with a fxml id 'cliOutput' that will be used as the terminal output.</li> * <li>A {@link Label} with a fxml id 'workingDirectory' that will be used to display the 'workingDirectory' label.</li> * <li>A {@link TextField} with a fxml id 'commandLine' that will be used to read and display the command line.</li> * </ul> * * @param fxmlUrl URL to a .fxml file containing the custom layout. * @return {@code this}, for chaining. */ public JavaFxCliBuilder setFxmlUrl(URL fxmlUrl) { this.fxmlUrl = Objects.requireNonNull(fxmlUrl, "fxmlUrl"); return this; } /** * @return A {@link Parent} that functions as a CLI built out of this builder's parameters. * @throws RuntimeException If an error occurs. */ public Parent build() { try { final CliCommandHierarchy hierarchy = CliCommandHierarchyImpl.from(hierarchyBuilder.build()); final URL fxmlUrl = getFxmlUrl(); final FXMLLoader loader = new FXMLLoader(fxmlUrl); final Parent cliNode = (Parent) loader.load(); // Label for 'working directory'. final Label workingDirectory = (Label) cliNode.lookup("#workingDirectory"); final CliGui gui = new JavaFxCliGui(workingDirectory); // Printers for cli output. final TextArea textArea = (TextArea) cliNode.lookup("#cliOutput"); textArea.setFocusTraversable(false); final CliPrinter out = new CliPrinter(new JavaFxCliOutput(textArea)); final CliPrinter err = new CliPrinter(new JavaFxCliOutput(textArea)); // TextField as command line. final TextField commandLine = (TextField) cliNode.lookup("#commandLine"); final CommandLineManager commandLineManager = new JavaFxCommandLineManager(commandLine); // Create the shell and the actual CLI. final CliShell shell = new CliShell.Builder(hierarchy, gui, out, err) .setMaxCommandHistory(maxCommandHistory) .build(); final Cli cli = new Cli(shell, commandLineManager); // Hook input events to CLI events. commandLine.requestFocus(); commandLine.addEventFilter(KeyEvent.KEY_PRESSED, new JavaFxCliEventHandler(cli)); return cliNode; } catch (Exception e) { throw SneakyException.sneakyThrow(e); } } private URL getFxmlUrl() { if (fxmlUrl != null) { return fxmlUrl; } return JavaFxCliBuilder.class.getResource("default_cli.fxml"); } }